using System;
using System.Collections.Generic;
using PaulRz.Threading;

namespace PaulRz.Util.Patterns
{
    /// <summary>
    /// Pipeline implementation
    /// We assume that each pipeline consists of:
    /// 1. One producer layer posting (producing) the messages incoming into the pipe
    /// 2. Several worker layers processing the messages
    /// 3. One consumer layer finilizing the message processing (consuming).
    /// 
    /// Each layer contain one or more elements
    /// </summary>
    public sealed class Pipeline : ActiveObject
    {
        /// <summary>
        /// Constructor with name.
        /// </summary>
        /// <param name="name"></param>
        public Pipeline(string name) : base(name)
        {
            
        }
        
        /// <summary>
        /// Constructor with name and logger specified
        /// </summary>
        /// <param name="name"></param>
        /// <param name="logger"></param>
        public Pipeline(string name, ILogInterface logger) : base(name, logger, 100)
        {
            
        }
        
        /// <summary>
        /// Constructor with name, logger and joinTime
        /// </summary>
        /// <param name="name"></param>
        /// <param name="logger"></param>
        /// <param name="joinTime"></param>
        public Pipeline(string name, ILogInterface logger, int joinTime) : base(name, logger, joinTime)
        {

        }

        #region Properties
        
        private LinkedList<ILayer> pipes = new LinkedList<ILayer>();

        /// <summary>
        /// Consumer layer
        /// </summary>
        public ILayer ConsumerLayer
        {
            get
            {
                return pipes.Last.Value;
            }
        }

        /// <summary>
        /// Producer layer
        /// </summary>
        public ILayer ProducerLayer
        {
            get
            {
                return pipes.First.Value;
            }
        }

        #endregion
        
        /// <summary>
        /// Returns the sum of all queues sizes
        /// </summary>
        /// <returns></returns>
        public int GetQueueSize()
        {
            int result = 0;
            foreach (ILayer layer in pipes)
            {
                result += layer.InputSize;
            }
            return result;
        }


        #region Element Addition

        /// <summary>
        /// Add producer
        /// </summary>
        /// <typeparam name="TOuput"></typeparam>
        /// <param name="element"></param>
        public void AddProducer<TOuput>(ProducerPipelineElement<TOuput> element)
            where TOuput:class 
        {
            AddProducer(new ProducerPipelineElement<TOuput>[] { element });
        }
        
        /// <summary>
        /// Add producers
        /// </summary>
        /// <typeparam name="TOuput"></typeparam>
        /// <param name="elements"></param>
        public void AddProducer<TOuput>(ProducerPipelineElement<TOuput>[] elements)
            where TOuput:class 
        {
            // TODO: optimize
            if (pipes.Count != 0)
                throw new ArgumentOutOfRangeException("element", elements.ToString(),
                                                      "Cannot add a producer in the middle of pipeline");
            
            PipelineLayer<NullIOType, TOuput> layer = new PipelineLayer<NullIOType, TOuput>(elements);
            layer.Input = NullIOType.Queue;
            
            AddPipelineElement(layer);
        }
        
        /// <summary>
        /// Add consumer
        /// </summary>
        /// <typeparam name="TInput"></typeparam>
        /// <param name="element"></param>
        public void AddConsumer<TInput>(ConsumerPipelineElement<TInput> element)
            where TInput:class 
        {
            AddConsumer(new ConsumerPipelineElement<TInput>[] { element });
        }
        
        /// <summary>
        /// Add consumers
        /// </summary>
        /// <typeparam name="TInput"></typeparam>
        /// <param name="elements"></param>
        public void AddConsumer<TInput>(ConsumerPipelineElement<TInput>[] elements)
            where TInput:class 
        {
            if (pipes.Count == 0)
                throw new ArgumentOutOfRangeException("element", elements,
                                                      "Consumer couldn't be in the beginning of pipeline");
            
            PipelineLayer<TInput, NullIOType> layer = new PipelineLayer<TInput, NullIOType>(elements);
            initLayer(layer);
            AddPipelineElement(layer);
        }
        
        /// <summary>
        /// Add worker
        /// </summary>
        /// <typeparam name="TInput"></typeparam>
        /// <typeparam name="TOutput"></typeparam>
        /// <param name="element"></param>
        public void AddWorker<TInput, TOutput>(WorkerPipelineElement<TInput, TOutput> element)
            where TInput : class
            where TOutput : class 
        {
            AddWorker(new WorkerPipelineElement<TInput, TOutput>[] { element });
        }
        
        /// <summary>
        /// Add workers
        /// </summary>
        /// <typeparam name="TInput"></typeparam>
        /// <typeparam name="TOutput"></typeparam>
        /// <param name="element"></param>
        public void AddWorker<TInput, TOutput>(WorkerPipelineElement<TInput, TOutput>[] element)
            where TInput:class 
            where TOutput:class 
        {
            if (pipes.Count == 0)
                throw new ArgumentOutOfRangeException("element", element.ToString(),
                                                      "Worker couldn't be in the beginning of pipeline");
            
            PipelineLayer<TInput, TOutput> layer = new PipelineLayer<TInput, TOutput>(element);
            initLayer(layer);
            AddPipelineElement(layer);
        }
        
        /// <summary>
        /// Add multiworker
        /// </summary>
        /// <typeparam name="TInput"></typeparam>
        /// <typeparam name="TOutput"></typeparam>
        /// <param name="element"></param>
        public void AddMultiWorker<TInput, TOutput>(MultiWorkerPipelineElement<TInput, TOutput> element)
            where TInput : class
            where TOutput : class 
        {
            AddMultiWorker(new MultiWorkerPipelineElement<TInput, TOutput>[] {element});
        }

        /// <summary>
        /// Add multiworkers
        /// </summary>
        /// <typeparam name="TInput"></typeparam>
        /// <typeparam name="TOutput"></typeparam>
        /// <param name="element"></param>
        public void AddMultiWorker<TInput, TOutput>(MultiWorkerPipelineElement<TInput, TOutput>[] element)
            where TInput : class
            where TOutput : class 
        {
            if (pipes.Count == 0)
                throw new ArgumentOutOfRangeException("element", element.ToString(),
                                                      "Worker couldn't be in the beginning of pipeline");

            PipelineLayer<TInput, TOutput> layer = new PipelineLayer<TInput, TOutput>(element);
            initLayer(layer);
            AddPipelineElement(layer);
        }

        private void AddPipelineElement<TInput, TOutput>(PipelineLayer<TInput, TOutput> layer)
            where TInput : class
            where TOutput : class 
        {
            pipes.AddLast(layer);
        }

        #endregion

        protected override void Run()
        {

        }

        #region Internal structs & methods

        private void initLayer<TInput, TOutput>(PipelineLayer<TInput, TOutput> layer)
            where TInput : class
            where TOutput : class 
        {
            ILayer previous = pipes.Last.Value;

            if (!previous.OutputType.Equals(typeof(TInput)))
                throw new ArgumentException("Previous queue type is not equals to workers' one",
                                            typeof(TInput).ToString());

            SynchronizedQueue<TInput> queue = new SynchronizedQueue<TInput>();
            layer.Input = queue;
            previous.Output = queue;
        }

        #endregion

        #region IRunnable implementation

        /// <summary>
        /// Start the pipeline
        /// </summary>
        public override void Start()
        {
            isStopping = false;
            foreach (ILayer layer in pipes)
            {
                layer.Start();
            }
        }

        /// <summary>
        /// Stop the pipeline
        /// </summary>
        public override void Stop()
        {
            isStopping = true;
            foreach (ILayer layer in pipes)
            {
                layer.Stop();
            }
        }

        /// <summary>
        /// Stop consumer layer
        /// </summary>
        public void StopConsumers()
        {
            isStopping = true;
            pipes.Last.Value.Stop();
        }

        /// <summary>
        /// Stop producer layer
        /// </summary>
        public void StopProducers()
        {
            isStopping = true;
            pipes.First.Value.Stop();
        }

        #endregion
    }
}
